using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using Microsoft.ApplicationBlocks.Data;
using System.Xml;
using System.Collections;
using System.Data;

namespace Microsoft.SharedSource.SqlServer.MDDE.Repository
{
    public class MDDETemplateInstance
    {
        MDDEPackageTemplate parentTemplate;
        int id;
        string name;
        InstanceConfigurableComponentCollection components;

        public MDDEPackageTemplate ParentTemplate
        {
            get { return parentTemplate; }
            set { parentTemplate = value; }
        }

        public int Id
        {
            get { return id; }
        }

        public string Name
        {
            get { return name; }
            set { name = value; }
        }

        public InstanceConfigurableComponentCollection Components
        {
            get
            {
                return components;
            }
        }

        private void RefreshComponents()
        {
            this.components =
                new InstanceConfigurableComponentCollection(this);

            // Look for the associated configurable components.
            DataSet configComponentInfo = SqlHelper.ExecuteDataset(
                parentTemplate.ParentRepository.RepositoryConnection,
                CommandType.Text,
                Resource.GetInstanceComponents,
                new SqlParameter("@templateInstanceId", this.Id));

            // Add in each of the above components to internal list.
            foreach (DataRow r in configComponentInfo.Tables[0].Rows)
            {
                // Create a new component object.
                InstanceConfigurableComponent objComp =
                    new InstanceConfigurableComponent(
                    this, r["ComponentType"].ToString(),
                    r["ComponentName"].ToString(),
                    r["ComponentUri"].ToString());

                // Add this component to list.
                this.components.Add(objComp);
            }
        }

        public XmlDocument Configuration
        {
            get
            {
                // Prepare the document.
                XmlDocument instanceConfigurationData = new XmlDocument();
                XmlNode instanceRoot = instanceConfigurationData.CreateElement("TemplateInstance");
                instanceConfigurationData.AppendChild(instanceRoot);

                #region Add configurable components.

                foreach (InstanceConfigurableComponent objComp in this.Components)
                {
                    XmlNode instanceComponent =
                        instanceConfigurationData.
                        CreateElement("ConfigurableComponent");

                    XmlAttribute uriAttrib =
                        instanceConfigurationData.
                        CreateAttribute("ComponentUri");

                    uriAttrib.Value = objComp.ComponentUri;

                    instanceComponent.Attributes.Append(uriAttrib);

                    XmlAttribute nameAttrib =
                        instanceConfigurationData.
                        CreateAttribute("ComponentName");

                    nameAttrib.Value = objComp.ComponentName;

                    instanceComponent.Attributes.Append(nameAttrib);

                    instanceRoot.AppendChild(instanceComponent);

                    #region Add instance scalar properties.

                    foreach (InstanceScalarProperty objProp in
                        objComp.ScalarProperties)
                    {
                        XmlNode instanceProperty =
                            instanceConfigurationData.
                            CreateElement("ConfigurableProperty");

                        XmlAttribute propName =
                            instanceConfigurationData.CreateAttribute("Name");

                        XmlAttribute propValue =
                            instanceConfigurationData.CreateAttribute("Value");

                        propName.Value = objProp.Name;

                        propValue.Value = objProp.Value;

                        instanceProperty.Attributes.Append(propName);

                        instanceProperty.Attributes.Append(propValue);

                        instanceComponent.AppendChild(instanceProperty);
                    }

                    #endregion

                    #region Add instance mappings.

                    foreach (InstanceMapping objMapping in
                        objComp.InstanceMappings)
                    {
                        // Create mapping element.
                        XmlNode instanceMapping =
                            instanceConfigurationData.
                            CreateElement("Mapping");

                        // Create mapping attributes.
                        XmlAttribute sourceCol =
                            instanceConfigurationData.
                            CreateAttribute("SourceColumnName");
                        XmlAttribute columnUsage =
                            instanceConfigurationData.
                            CreateAttribute("SourceColumnUsage");
                        XmlAttribute destinationCol =
                            instanceConfigurationData.
                            CreateAttribute("DestinationColumnName");

                        // Provide values to attributes.
                        sourceCol.Value = objMapping.SourceColumnName;
                        columnUsage.Value =
                            objMapping.SourceColumnUsage ? "True" : "False";
                        destinationCol.Value = objMapping.DestinationColumnName;

                        // Append in mapping attributes.
                        instanceMapping.Attributes.Append(sourceCol);
                        instanceMapping.Attributes.Append(columnUsage);
                        instanceMapping.Attributes.Append(destinationCol);

                        instanceComponent.AppendChild(instanceMapping);
                    }

                    #endregion

                    #region Add column collections.

                    foreach (InstanceColumnCollection objColl in
                        objComp.ColumnCollections)
                    {
                        XmlElement xnodeInput =
                            instanceConfigurationData.
                            CreateElement("ColumnCollection");

                        XmlAttribute collAttrib =
                            instanceConfigurationData.
                            CreateAttribute("Name");

                        collAttrib.Value = objColl.Name;

                        xnodeInput.Attributes.Append(collAttrib);

                        collAttrib =
                            instanceConfigurationData.
                            CreateAttribute("Type");

                        collAttrib.Value = objColl.CollectionType;

                        xnodeInput.Attributes.Append(collAttrib);

                        instanceComponent.AppendChild(xnodeInput);

                        #region Add collection properties.

                        foreach (InstanceColumnCollectionProperty objCollProp
                            in objColl.Properties)
                        {
                            // Create the 'ConfigurableProperty' element.
                            XmlElement xnodeProperty =
                                instanceConfigurationData.
                                CreateElement("ConfigurableProperty");

                            // Create attributes.
                            XmlAttribute propAttrib =
                                instanceConfigurationData.
                                CreateAttribute("Name");

                            propAttrib.Value = objCollProp.Name;

                            xnodeProperty.Attributes.Append(propAttrib);

                            propAttrib =
                                instanceConfigurationData.
                                CreateAttribute("Value");

                            propAttrib.Value = objCollProp.Value;

                            xnodeProperty.Attributes.Append(propAttrib);

                            xnodeInput.AppendChild(xnodeProperty);
                        }

                        #endregion

                        #region Add collection columns.

                        foreach (InstanceColumn objCol in
                            objColl.InstanceColumns)
                        {
                            // Create a 'Column' element.
                            XmlElement xnodeColumn =
                                instanceConfigurationData.
                                CreateElement("Column");

                            XmlAttribute colAttrib =
                                instanceConfigurationData.
                                CreateAttribute("Name");

                            colAttrib.Value = objCol.Name;

                            xnodeColumn.Attributes.Append(colAttrib);

                            xnodeInput.AppendChild(xnodeColumn);

                            #region Add column properties.

                            foreach (InstanceColumnProperty objColProp in
                                objCol.Properties)
                            {
                                // Create the 'ConfigurableProperty' element.
                                XmlElement xnodeProperty =
                                    instanceConfigurationData.
                                    CreateElement("ConfigurableProperty");

                                // Create attributes.
                                XmlAttribute propAttrib =
                                    instanceConfigurationData.
                                    CreateAttribute("Name");

                                propAttrib.Value = objColProp.Name;

                                xnodeProperty.Attributes.Append(propAttrib);

                                propAttrib =
                                    instanceConfigurationData.
                                    CreateAttribute("Value");

                                propAttrib.Value = objColProp.Value;

                                xnodeProperty.Attributes.Append(propAttrib);

                                xnodeColumn.AppendChild(xnodeProperty);
                            }

                            #endregion
                        }

                        #endregion
                    }

                    #endregion
                }

                #endregion

                return instanceConfigurationData;
            }
        }

        public MDDETemplateInstance(MDDEPackageTemplate parentTemplate,
            string instanceName)
        {
            this.parentTemplate = parentTemplate;
            this.name = instanceName;

            // Get instance details from the repository.
            SqlParameter[] spParams = new SqlParameter[2];
            spParams[0] = new SqlParameter(
                "@templateId", this.parentTemplate.Id);
            spParams[1] = new SqlParameter(
                "@templateInstanceName", this.Name);

            DataSet instanceInfo =
                SqlHelper.ExecuteDataset(
                parentTemplate.ParentRepository.RepositoryConnection,
                CommandType.Text,
                Resource.GetTemplateInstanceInfo, spParams);

            // Fill in instance properties.
            if (instanceInfo.Tables[0].Rows.Count > 0)
            {
                this.id =
                    Convert.ToInt32(
                    instanceInfo.Tables[0].Rows[0]["InstanceId"]);
            }

            // Create the component information.
            RefreshComponents();
        }

        public void Create()
        {
            // Verify that we have sufficient information.
            if (string.IsNullOrEmpty(this.name) ||
                parentTemplate == null)
                throw new Exception(
                    "Name and parent template of a " +
                    "template instance cannot be null");

            // Begin a transaction.
            SqlTransaction tn =
                this.parentTemplate.ParentRepository.
                RepositoryConnection.BeginTransaction();

            try
            {
                // Create a new template instance.
                SqlParameter[] spParams = new SqlParameter[2];
                spParams[0] = new SqlParameter(
                    "@templateName", this.parentTemplate.Name);
                spParams[1] = new SqlParameter(
                    "@instanceName", this.Name);

                this.id = Convert.ToInt32(
                    SqlHelper.ExecuteScalar(tn,
                    "sp_CreateTemplateInstance", spParams));

                // Create the configurable components.
                foreach (InstanceConfigurableComponent comp
                    in this.Components)
                    comp.Create(tn);

                // Commit the transaction.
                tn.Commit();
            }
            catch (Exception ex)
            {
                tn.Rollback();
                throw ex;
            }
        }

        public void Delete()
        {
            // Delete template instance.
            SqlHelper.ExecuteScalar(
                this.parentTemplate.ParentRepository.RepositoryConnection,
                CommandType.StoredProcedure,
                "sp_DeleteTemplateInstance",
                new SqlParameter("templateInstanceID", this.Id));
        }

        public void Update()
        {
            // Begin a transaction.
            SqlTransaction txn =
                this.parentTemplate.ParentRepository.
                RepositoryConnection.BeginTransaction();

            try
            {
                #region Update the components.

                // Get the components list.
                DataSet componentsInfo =
                    SqlHelper.ExecuteDataset(txn, CommandType.Text,
                    Resource.GetInstanceComponents,
                    new SqlParameter[] { new SqlParameter("@templateInstanceId", this.Id) });

                List<string[]> compToBeDeleted =
                    new List<string[]>();

                List<InstanceConfigurableComponent> compToBeUpdated =
                    new List<InstanceConfigurableComponent>();

                List<InstanceConfigurableComponent> compToBeInserted =
                    new List<InstanceConfigurableComponent>();

                foreach (DataRow r0 in componentsInfo.Tables[0].Rows)
                {
                    // Find all the components that 
                    // are now not present in the collection 
                    // and delete them from the repository.
                    if (!this.Components.ContainsConfigurableComponent(r0["ComponentUri"].ToString()))
                        compToBeDeleted.Add(new string[]{
                            r0["ComponentType"].ToString(),
                            r0["ComponentName"].ToString(),
                            r0["ComponentUri"].ToString() });
                }

                bool componentExists;
                foreach (InstanceConfigurableComponent component in this.Components)
                {
                    componentExists = false;

                    // Check if this component is already in the repository.
                    foreach (DataRow r0 in componentsInfo.Tables[0].Rows)
                    {
                        if (r0["ComponentUri"].ToString().Equals(
                            component.ComponentUri,
                            StringComparison.InvariantCultureIgnoreCase))
                        {
                            compToBeUpdated.Add(component);
                            componentExists = true;
                            break;
                        }
                    }

                    // Find new members in the collection 
                    // and insert them into the repository.
                    if (!componentExists)
                        compToBeInserted.Add(component);
                }

                // Delete the collections.
                foreach (string[] componentDetails in compToBeDeleted)
                {
                    InstanceConfigurableComponent component =
                        new InstanceConfigurableComponent(
                        this, componentDetails[0], componentDetails[1], componentDetails[2], txn);

                    component.Delete(txn);
                }

                // Update the collections.
                foreach (InstanceConfigurableComponent component in compToBeUpdated)
                    component.Update(txn);

                // Insert new collections.
                foreach (InstanceConfigurableComponent component in compToBeInserted)
                    component.Create(txn);

                #endregion

                //// Update the configurable components.
                //foreach (InstanceConfigurableComponent comp
                //    in this.Components)
                //    comp.Update(tn);

                //// Commit the transaction.
                //tn.Commit();

                // Commit the transaction.
                txn.Commit();
            }
            catch (Exception ex)
            {
                txn.Rollback();
                throw ex;
            }
        }

        public XmlDocument GetChangesXml()
        {
            XmlDocument changesXml = new XmlDocument();

            // Create the root element.
            XmlElement rootElement =
                changesXml.CreateElement("PackageChanges");

            changesXml.AppendChild(rootElement);

            // Add in the components.
            foreach (InstanceConfigurableComponent comp in this.Components)
            {
                // Skip this component if there are no properties or 
                // mappings or column collections defined for it.
                if (comp.ScalarProperties.Count == 0 &&
                    comp.InstanceMappings.Count == 0 &&
                    comp.ColumnCollections.Count == 0)
                    continue;

                // Create the component element.
                XmlElement xmlComp;
                switch (comp.ComponentType)
                {
                    case "Component":
                    case "Executable":
                        xmlComp = changesXml.CreateElement("Component");
                        break;
                    default:
                        xmlComp =
                            changesXml.CreateElement(comp.ComponentType);
                        break;
                }

                // Add in the component properties. 
                XmlAttribute attrib =
                    changesXml.CreateAttribute("Name");
                attrib.Value = comp.ComponentName;
                xmlComp.Attributes.Append(attrib);

                attrib =
                    changesXml.CreateAttribute("URL");
                attrib.Value = comp.ComponentUri;
                xmlComp.Attributes.Append(attrib);

                #region Add the 'Properties' node.
                if (comp.ScalarProperties.Count > 0)
                {
                    XmlElement xmlProperties =
                        changesXml.CreateElement("Properties");

                    // Add the scalar properties.
                    foreach (InstanceScalarProperty prop in comp.ScalarProperties)
                    {
                        // Continue if the value is not specified.
                        if (string.IsNullOrEmpty(prop.Value))
                            continue;

                        XmlElement xmlProp =
                            changesXml.CreateElement("Property");

                        XmlAttribute propAttrib =
                            changesXml.CreateAttribute("Name");
                        propAttrib.Value = prop.Name;
                        xmlProp.Attributes.Append(propAttrib);

                        propAttrib =
                            changesXml.CreateAttribute("Value");
                        propAttrib.Value = prop.Value;
                        xmlProp.Attributes.Append(propAttrib);

                        // Attach this property to 'Properties' node.
                        xmlProperties.AppendChild(xmlProp);
                    }

                    // Append the 'Properties' node to component node.
                    xmlComp.AppendChild(xmlProperties);
                }
                #endregion End of 'Properties'

                #region Add the 'CollectionInformation' node.
                if (comp.ColumnCollections.Count > 0)
                {
                    XmlElement xmlCollectionInfo =
                        changesXml.CreateElement("CollectionInformation");

                    #region Add the 'Collection' node.
                    foreach (InstanceColumnCollection collection
                        in comp.ColumnCollections)
                    {
                        XmlElement xmlCollection =
                            changesXml.CreateElement("Collection");

                        XmlAttribute collAttrib =
                            changesXml.CreateAttribute("Name");
                        collAttrib.Value = collection.Name;
                        xmlCollection.Attributes.Append(collAttrib);

                        collAttrib =
                            changesXml.CreateAttribute("Type");
                        collAttrib.Value = collection.CollectionType;
                        xmlCollection.Attributes.Append(collAttrib);

                        #region Add the 'Properties' node.
                        if (collection.Properties.Count > 0)
                        {
                            XmlElement xmlCollProperties =
                                changesXml.CreateElement("Properties");

                            // Add the properties.
                            foreach (InstanceColumnCollectionProperty prop
                                in collection.Properties)
                            {
                                XmlElement xmlCollProp =
                                    changesXml.CreateElement("Property");

                                XmlAttribute collPropAttrib =
                                    changesXml.CreateAttribute("Name");
                                collPropAttrib.Value = prop.Name;
                                xmlCollProp.Attributes.Append(collPropAttrib);

                                collPropAttrib =
                                    changesXml.CreateAttribute("Value");
                                collPropAttrib.Value = prop.Value;
                                xmlCollProp.Attributes.Append(collPropAttrib);

                                // Attach this property to 'Properties' node.
                                xmlCollProperties.AppendChild(xmlCollProp);
                            }

                            // Append the 'Properties' node to 'Collection' node.
                            xmlCollection.AppendChild(xmlCollProperties);
                        }
                        #endregion

                        #region Add the 'Columns' node.
                        if (collection.InstanceColumns.Count > 0)
                        {
                            XmlElement xmlColumns =
                                changesXml.CreateElement("Columns");

                            // Add the 'Column' node.
                            foreach (InstanceColumn objColumn
                                in collection.InstanceColumns)
                            {
                                XmlElement xmlColumn =
                                    changesXml.CreateElement("Column");

                                XmlAttribute xmlColumnAttrib =
                                    changesXml.CreateAttribute("Name");
                                xmlColumnAttrib.Value = objColumn.Name;
                                xmlColumn.Attributes.Append(xmlColumnAttrib);

                                #region Add the 'Properties' node.
                                if (objColumn.Properties.Count > 0)
                                {
                                    XmlElement xmlColProperties =
                                        changesXml.CreateElement("Properties");

                                    // Add the properties.
                                    foreach (InstanceColumnProperty prop
                                        in objColumn.Properties)
                                    {
                                        XmlElement xmlColProperty =
                                            changesXml.CreateElement("Property");

                                        XmlAttribute xmlColPropertyAttrib =
                                            changesXml.CreateAttribute("Name");
                                        xmlColPropertyAttrib.Value = prop.Name;
                                        xmlColProperty.Attributes.Append(xmlColPropertyAttrib);

                                        xmlColPropertyAttrib =
                                            changesXml.CreateAttribute("Value");
                                        xmlColPropertyAttrib.Value = prop.Value;
                                        xmlColProperty.Attributes.Append(xmlColPropertyAttrib);

                                        // Attach this property to 'Properties' node.
                                        xmlColProperties.AppendChild(xmlColProperty);
                                    }

                                    // Append the 'Properties' node to 'Column' node.
                                    xmlColumn.AppendChild(xmlColProperties);
                                }
                                #endregion

                                // Attach the 'Column' node to 'Columns' node.
                                xmlColumns.AppendChild(xmlColumn);
                            }

                            // Append the 'Columns' node to 'Collection' node.
                            xmlCollection.AppendChild(xmlColumns);
                        }
                        #endregion

                        // Attach this collection to 
                        // 'CollectionInformation' node.
                        xmlCollectionInfo.AppendChild(xmlCollection);
                    }
                    #endregion

                    // Append the 'CollectionInformation' node to component node.
                    xmlComp.AppendChild(xmlCollectionInfo);
                }
                #endregion End of 'CollectionInformation' node.

                #region Add the 'Mappings' node.
                if (comp.InstanceMappings.Count > 0)
                {
                    XmlElement xmlMappings =
                        changesXml.CreateElement("Mappings");

                    // Add the scalar properties.
                    foreach (InstanceMapping mapping in comp.InstanceMappings)
                    {
                        XmlElement xmlProp =
                            changesXml.CreateElement("Mapping");

                        XmlAttribute propAttrib =
                            changesXml.CreateAttribute("SourceColumn");
                        propAttrib.Value = mapping.SourceColumnName;
                        xmlProp.Attributes.Append(propAttrib);

                        propAttrib =
                            changesXml.CreateAttribute("Usage");
                        propAttrib.Value =
                            mapping.SourceColumnUsage ? "True" : "False";
                        xmlProp.Attributes.Append(propAttrib);

                        propAttrib =
                            changesXml.CreateAttribute("DestinationColumn");
                        propAttrib.Value = mapping.DestinationColumnName;
                        xmlProp.Attributes.Append(propAttrib);

                        // Attach this property to 'Properties' node.
                        xmlMappings.AppendChild(xmlProp);
                    }

                    // Append the 'Mappings' node to component node.
                    xmlComp.AppendChild(xmlMappings);
                }
                #endregion

                // Append this component to root.
                rootElement.AppendChild(xmlComp);
            }

            return changesXml;
        }

        public void ApplyProperties(XmlDocument changesXml, bool createAfresh)
        {
            // Clear all the configurable components.
            if (createAfresh)
                this.Components.Clear();

            // Parse the data document and create components.
            foreach (XmlNode xComponent in changesXml.FirstChild.ChildNodes)
            {
                string componentType = xComponent.Name;
                string componentName = xComponent.Attributes["Name"].Value;
                string componentUri = xComponent.Attributes["URL"] == null ?
                    string.Empty : xComponent.Attributes["URL"].Value;

                // Create the component object.
                InstanceConfigurableComponent comp;

                // Try locating the component.
                if (this.Components.ContainsConfigurableComponent(componentUri))
                    // Found!, select it for further processing.
                    comp = this.Components[componentUri];
                else
                    // Create a new component.
                    comp =
                        new InstanceConfigurableComponent(
                        this, componentType, componentName, componentUri);

                // Apply fresh properties.
                comp.ApplyProperties(xComponent, createAfresh);

                // Add it to list.
                if (!this.Components.ContainsConfigurableComponent(componentUri))
                    this.Components.Add(comp);
            }

            // Remove the components not found in the document.
            List<InstanceConfigurableComponent> toBeRemoved =
                new List<InstanceConfigurableComponent>();
            foreach (InstanceConfigurableComponent component in this.Components)
                if (changesXml.SelectSingleNode(
                    "//*[@URL=\"" + component.ComponentUri + "\"]") == null)
                    toBeRemoved.Add(component);
            foreach (InstanceConfigurableComponent component in toBeRemoved)
                this.Components.Remove(component);
        }
    }

    public sealed class TemplateInstanceCollection : IEnumerable
    {
        #region Fields

        private Dictionary<string, MDDETemplateInstance> templateInstances =
            new Dictionary<string, MDDETemplateInstance>();

        MDDEPackageTemplate parent;

        #endregion

        #region Properties

        public MDDEPackageTemplate Parent
        {
            get { return parent; }
        }

        public MDDETemplateInstance this[string templateName]
        {
            get { return templateInstances[templateName]; }
        }

        #endregion

        #region Methods

        internal TemplateInstanceCollection(MDDEPackageTemplate parent)
        {
            this.parent = parent;
        }

        public void Add(MDDETemplateInstance templateInstance)
        {
            templateInstances.Add(templateInstance.Name, templateInstance);
        }

        public void Remove(MDDETemplateInstance templateInstance)
        {
            templateInstances.Remove(templateInstance.Name);
        }

        public bool ContainsTemplateInstance(string instanceName)
        {
            return templateInstances.ContainsKey(instanceName);
        }

        #endregion

        #region IEnumerable Members

        public IEnumerator GetEnumerator()
        {
            return templateInstances.Values.GetEnumerator();
        }

        #endregion
    }
}
